
// ===============================================================================================================
// -*- C++ -*-
//
// Image.cpp - Implementation of the TGA image loader and the image factory.
//
// Copyright (c) 2006-2007 David Henry
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// Modified by: Guilherme R. Lampert - March 2011
// guilherme.ronaldo.lampert@gmail.com
//
// ===============================================================================================================

#include <Image.hpp>
#include <cstdio>

// =========================================================
// Image Class Implementation
// =========================================================

// I heavily modified the original Image class to add support for reference counting
// and cloning. The pixels are now stored inside a MemoryBuffer object.
// Also removed some unused stuff...

bool Image::IsPowerOfTwo(void) const
{
	int m;

	for (m = 1; m < width; m <<= 1)
		;

	if (m != width)
	{
		return (false);
	}

	for (m = 1; m < height; m <<= 1)
		;

	if (m != height)
	{
		return (false);
	}

	return (true);
}

bool Image::Fail(void) const
{
	if (pixels == 0)
	{
		return (true);
	}

	// Failed if the pixels are invalid.
	return (pixels->Fail());
}

Image * Image::Clone(void) const
{
	Image * img = 0;

	if (!this->Fail()) // If this image is okey:
	{
		try {
			img = new Image(*this);

			if (img->Fail())
			{
				img->Release();
				img = 0;
			}
		}
		catch (...) {

			if (img)
			{
				img->Release();
				img = 0;
			}
		}
	}

	return (img);
}

unsigned long Image::AddRef(void) const
{
	return (++refCount);
}

unsigned long Image::Release(void) const
{
	if (--refCount == 0)
	{
		// Time to die...
		delete this;
		return (0);
	}

	return (refCount);
}

unsigned long Image::ReferenceCount(void) const
{
	return (refCount);
}

Image::~Image(void)
{
	if (pixels != 0)
	{
		pixels->Release();
	}
}

// =========================================================
// ImageTGA Class Implementation
// =========================================================

// Pixel's component table access
const int ImageTGA::rgbaTable[4] = { 2, 1, 0, 3 };
const int ImageTGA::bgraTable[4] = { 0, 1, 2, 3 };

ImageTGA::ImageTGA(const MemoryBuffer * memory, const std::string & originalFile) : Image()
{
	readTGAData(memory, originalFile);
}

ImageTGA::ImageTGA(const std::string & fileName) : Image()
{
	MemoryBuffer * buf = MemoryBuffer::CreateFromFileData(fileName);

	if (buf != 0)
	{
		readTGAData(buf, fileName);
		buf->Release();
	}
}

void ImageTGA::getTextureInfo(const ImageTGA::TGA_Header * header)
{
	width  = header->width;
	height = header->height;

	switch (header->image_type)
	{
		case 3:  // grayscale 8 bits
		case 11: // grayscale 8 bits (RLE)
		{
			if (header->pixel_depth == 8)
			{
				format = GL_LUMINANCE;
				components = 1;
			}
			else // 16 bits
			{
				format = GL_LUMINANCE_ALPHA;
				components = 2;
			}
			break;
		}

		case 1:    // 8 bits color index
		case 2:    // BGR 16-24-32 bits
		case 9:    // 8 bits color index (RLE)
		case 10:   // BGR 16-24-32 bits (RLE)
		{
			// 8 bits and 16 bits images will be converted to 24 bits
			if (header->pixel_depth <= 24)
			{
				format = GLEW_EXT_bgra ? GL_BGR : GL_RGB;
				components = 3;
			}
			else // 32 bits
			{
				format = GLEW_EXT_bgra ? GL_BGRA : GL_RGBA;
				components = 4;
			}
			break;
		}
	}
}

bool ImageTGA::readTGAData(const MemoryBuffer * memory, const std::string & originalFile)
{
	if (!memory || memory->Fail())
	{
		return (false);
	}

	const GLubyte * colormap = 0;
	const GLubyte * data_ptr;
	const TGA_Header * tgaHeader;

	fileName = originalFile;
	data_ptr = reinterpret_cast<const GLubyte *>(memory->GetBufferPointer());
	standardCoordSystem = true;

	// Read TGA header
	tgaHeader = reinterpret_cast<const TGA_Header *>(data_ptr);
	data_ptr += sizeof(TGA_Header) + tgaHeader->id_lenght;

	// Get image information
	getTextureInfo(tgaHeader);

	// Memory allocation for pixel data
	pixels = MemoryBuffer::Create(width * height * components);

	if (!pixels)
	{
		LOG_ERROR("Failed to allocate memory to load the image: " << fileName);
		return (false);
	}

	// Read color map, if present
	if (tgaHeader->colormap_type)
	{
		// NOTE: color map is stored in BGR
		colormap = data_ptr;
		data_ptr += tgaHeader->cm_length * (tgaHeader->cm_size >> 3);
	}

	// Decode image data
	switch (tgaHeader->image_type)
	{
	case 0:
		// No data
		break;

	case 1:
		// Uncompressed 8 bits color index
		readTGA8bits(data_ptr, colormap);
		break;

	case 2:
		// Uncompressed 16-24-32 bits
		switch (tgaHeader->pixel_depth)
		{
		case 16:
			readTGA16bits(data_ptr);
			break;

		case 24:
			readTGA24bits(data_ptr);
			break;

		case 32:
			readTGA32bits(data_ptr);
			break;
		}
		break;

	case 3:
		// Uncompressed 8 or 16 bits grayscale
		if (tgaHeader->pixel_depth == 8)
		{
			readTGAgray8bits(data_ptr);
		}
		else // 16 bits
		{
			readTGAgray16bits(data_ptr);
		}
		break;

	case 9:
		// RLE compressed 8 bits color index
		readTGA8bitsRLE (data_ptr, colormap);
		break;

	case 10:
		// RLE compressed 16-24-32 bits
		switch (tgaHeader->pixel_depth)
		{
		case 16:
			readTGA16bitsRLE(data_ptr);
			break;

		case 24:
			readTGA24bitsRLE(data_ptr);
			break;

		case 32:
			readTGA32bitsRLE(data_ptr);
			break;
		}
		break;

	case 11:
		// RLE compressed 8 or 16 bits grayscale
		if (tgaHeader->pixel_depth == 8)
		{
			readTGAgray8bitsRLE(data_ptr);
		}
		else // 16 bits
		{
			readTGAgray16bitsRLE(data_ptr);
		}
		break;

	default:
		// Image type is not correct, free memory and quit
		pixels->Release();
		pixels = 0;

		LOG_ERROR("Unknown TGA image type! Image: " << fileName);
		return (false);
	}

	return (true);
}

void ImageTGA::readTGA8bits(const GLubyte * data, const GLubyte * colormap)
{
	// Read 8 bits pixel data from TGA file.

	const GLubyte * pData = data;
	const int * compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

	GLubyte * imgPixels = reinterpret_cast<GLubyte * >(pixels->GetBufferPointer());

	for (int i = 0; i < width * height; ++i)
	{
		// Read index color byte
		GLubyte color = *(pData++);

		// Convert to BGR/RGB 24 bits
		imgPixels[(i * 3) + compTable[0]] = colormap[(color * 3) + 0];
		imgPixels[(i * 3) + compTable[1]] = colormap[(color * 3) + 1];
		imgPixels[(i * 3) + compTable[2]] = colormap[(color * 3) + 2];
	}
}

void ImageTGA::readTGA16bits(const GLubyte * data)
{
	// Read 16 bits pixel data from TGA file.

	const GLubyte * pData = data;
	const int * compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

	GLubyte * imgPixels = reinterpret_cast<GLubyte * >(pixels->GetBufferPointer());

	for (int i = 0; i < width * height; ++i, pData += 2)
	{
		// Read color word
		unsigned short color = pData[0] + (pData[1] << 8);

		// convert to BGR/RGB 24 bits
		imgPixels[(i * 3) + compTable[2]] = (((color & 0x7C00) >> 10) << 3);
		imgPixels[(i * 3) + compTable[1]] = (((color & 0x03E0) >>  5) << 3);
		imgPixels[(i * 3) + compTable[0]] = (((color & 0x001F) >>  0) << 3);
	}
}

void ImageTGA::readTGA24bits(const GLubyte * data)
{
	// Read 24 bits pixel data from TGA file.

	GLubyte * imgPixels = reinterpret_cast<GLubyte * >(pixels->GetBufferPointer());

	if (GLEW_EXT_bgra)
	{
		memcpy(imgPixels, data, width * height * 3);
	}
	else
	{
		const GLubyte *pData = data;

		for (int i = 0; i < width * height; ++i)
		{
			// Read RGB 24 bits pixel
			imgPixels[(i * 3) + rgbaTable[0]] = *(pData++);
			imgPixels[(i * 3) + rgbaTable[1]] = *(pData++);
			imgPixels[(i * 3) + rgbaTable[2]] = *(pData++);
		}
	}
}

void ImageTGA::readTGA32bits (const GLubyte * data)
{
	// Read 32 bits pixel data from TGA file.

	GLubyte * imgPixels = reinterpret_cast<GLubyte * >(pixels->GetBufferPointer());

	if (GLEW_EXT_bgra)
	{
		memcpy(imgPixels, data, width * height * 4);
	}
	else
	{
		const GLubyte *pData = data;

		for (int i = 0; i < width * height; ++i)
		{
			// Read RGB 32 bits pixel
			imgPixels[(i * 4) + rgbaTable[0]] = *(pData++);
			imgPixels[(i * 4) + rgbaTable[1]] = *(pData++);
			imgPixels[(i * 4) + rgbaTable[2]] = *(pData++);
			imgPixels[(i * 4) + rgbaTable[3]] = *(pData++);
		}
	}
}

void ImageTGA::readTGAgray8bits(const GLubyte * data)
{
	// Read grey 8 bits pixel data from TGA file.

	memcpy(pixels->GetBufferPointer(), data, width * height);
}

void ImageTGA::readTGAgray16bits(const GLubyte * data)
{
	// Read grey 16 bits pixel data from TGA file.

	memcpy(pixels->GetBufferPointer(), data, width * height * 2);
}

void ImageTGA::readTGA8bitsRLE(const GLubyte * data, const GLubyte * colormap)
{
	// Read 8 bits pixel data from TGA file with RLE compression.

	GLubyte * ptr = reinterpret_cast<GLubyte * >(pixels->GetBufferPointer());
	const GLubyte * pData = data;
	const int * compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

	const GLubyte * imgPixels = ptr;

	while (ptr < imgPixels + (width * height) * 3)
	{
		// Read first byte
		GLubyte packet_header = *(pData++);
		int size = 1 + (packet_header & 0x7f);

		if (packet_header & 0x80)
		{
			// Run-length packet
			GLubyte color = *(pData++);

			for (int i = 0; i < size; ++i, ptr += 3)
			{
				ptr[0] = colormap[(color * 3) + compTable[0]];
				ptr[1] = colormap[(color * 3) + compTable[1]];
				ptr[2] = colormap[(color * 3) + compTable[2]];
			}
		}
		else
		{
			// Non run-length packet
			for (int i = 0; i < size; ++i, ptr += 3)
			{
				GLubyte color = *(pData++);

				ptr[0] = colormap[(color * 3) + compTable[0]];
				ptr[1] = colormap[(color * 3) + compTable[1]];
				ptr[2] = colormap[(color * 3) + compTable[2]];
			}
		}
	}
}

void ImageTGA::readTGA16bitsRLE(const GLubyte * data)
{
	// Read 16 bits pixel data from TGA file with RLE compression.

	GLubyte * ptr = reinterpret_cast<GLubyte * >(pixels->GetBufferPointer());
	const GLubyte * pData = data;
	const int * compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

	const GLubyte * imgPixels = ptr;

	while (ptr < imgPixels + (width * height) * 3)
	{
		// Read first byte
		GLubyte packet_header = *(pData++);
		int size = 1 + (packet_header & 0x7f);

		if (packet_header & 0x80)
		{
			// Run-length packet
			unsigned short color = pData[0] + (pData[1] << 8);
			pData += 2;

			for (int i = 0; i < size; ++i, ptr += 3)
			{
				ptr[compTable[2]] = (((color & 0x7C00) >> 10) << 3);
				ptr[compTable[1]] = (((color & 0x03E0) >>  5) << 3);
				ptr[compTable[0]] = (((color & 0x001F) >>  0) << 3);
			}
		}
		else
		{
			// Non run-length packet
			for (int i = 0; i < size; ++i, ptr += 3)
			{
				unsigned short color = pData[0] + (pData[1] << 8);
				pData += 2;

				ptr[compTable[2]] = (((color & 0x7C00) >> 10) << 3);
				ptr[compTable[1]] = (((color & 0x03E0) >>  5) << 3);
				ptr[compTable[0]] = (((color & 0x001F) >>  0) << 3);
			}
		}
	}
}

void ImageTGA::readTGA24bitsRLE(const GLubyte * data)
{
	// Read 24 bits pixel data from TGA file with RLE compression.

	GLubyte * ptr = reinterpret_cast<GLubyte * >(pixels->GetBufferPointer());
	const GLubyte * pData = data;
	const int * compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

	const GLubyte * imgPixels = ptr;

	while (ptr < imgPixels + (width * height) * 3)
	{
		// Read first byte
		GLubyte packet_header = *(pData++);
		int size = 1 + (packet_header & 0x7f);

		if (packet_header & 0x80)
		{
			// Run-length packet
			for (int i = 0; i < size; ++i, ptr += 3)
			{
				ptr[0] = pData[compTable[0]];
				ptr[1] = pData[compTable[1]];
				ptr[2] = pData[compTable[2]];
			}

			pData += 3;
		}
		else
		{
			// Non run-length packet
			for (int i = 0; i < size; ++i, ptr += 3)
			{
				ptr[0] = pData[compTable[0]];
				ptr[1] = pData[compTable[1]];
				ptr[2] = pData[compTable[2]];
				pData += 3;
			}
		}
	}
}

void ImageTGA::readTGA32bitsRLE(const GLubyte * data)
{
	// Read 32 bits pixel data from TGA file with RLE compression.

	GLubyte * ptr = reinterpret_cast<GLubyte * >(pixels->GetBufferPointer());
	const GLubyte * pData = data;
	const int * compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

	const GLubyte * imgPixels = ptr;

	while (ptr < imgPixels + (width * height) * 4)
	{
		// Read first byte
		GLubyte packet_header = *(pData++);
		int size = 1 + (packet_header & 0x7f);

		if (packet_header & 0x80)
		{
			// Run-length packet
			for (int i = 0; i < size; ++i, ptr += 4)
			{
				ptr[0] = pData[compTable[0]];
				ptr[1] = pData[compTable[1]];
				ptr[2] = pData[compTable[2]];
				ptr[3] = pData[compTable[3]];
			}

			pData += 4;
		}
		else
		{
			// Non run-length packet
			for (int i = 0; i < size; ++i, ptr += 4)
			{
				ptr[0] = pData[compTable[0]];
				ptr[1] = pData[compTable[1]];
				ptr[2] = pData[compTable[2]];
				ptr[3] = pData[compTable[3]];
				pData += 4;
			}
		}
	}
}

void ImageTGA::readTGAgray8bitsRLE(const GLubyte * data)
{
	// Read grey 8 bits pixel data from TGA file with RLE compression.

	GLubyte * ptr = reinterpret_cast<GLubyte * >(pixels->GetBufferPointer());
	const GLubyte *pData = data;

	const GLubyte * imgPixels = ptr;

	while (ptr < imgPixels + (width * height))
	{
		// Read first byte
		GLubyte packet_header = *(pData++);
		int size = 1 + (packet_header & 0x7f);

		if (packet_header & 0x80)
		{
			// Run-length packet
			GLubyte color = *(pData++);
			memset(ptr, color, size);
			ptr += size;
		}
		else
		{
			// Non run-length packet
			memcpy(ptr, pData, size);
			ptr += size;
			pData += size;
		}
	}
}

void ImageTGA::readTGAgray16bitsRLE(const GLubyte * data)
{
	// Read grey 16 bits pixel data from TGA file with RLE compression.

	GLubyte * ptr = reinterpret_cast<GLubyte * >(pixels->GetBufferPointer());
	const GLubyte *pData = data;

	const GLubyte * imgPixels = ptr;

	while (ptr < imgPixels + (width * height) * 2)
	{
		// Read first byte
		GLubyte packet_header = *(pData++);
		int size = 1 + (packet_header & 0x7f);

		if (packet_header & 0x80)
		{
			// Run-length packet
			GLubyte color = *(pData++);
			GLubyte alpha = *(pData++);

			for (int i = 0; i < size; ++i, ptr += 2)
			{
				ptr[0] = color;
				ptr[1] = alpha;
			}
		}
		else
		{
			// Non run-length packet
			memcpy (ptr, pData, size * 2);
			ptr += size * 2;
			pData += size * 2;
		}
	}
}

// =========================================================
// ImageFactory Class Implementation
// =========================================================

Image * ImageFactory::CreateImageFromFile(const std::string & fileName)
{
	std::string ext;
	Image * img;

	// Extract file extension:
	ext.assign(fileName, fileName.find_last_of('.') + 1, std::string::npos);

	if (stricmp(ext.c_str(), "tga") == 0) // Case-insensitive compare
	{
		img = new ImageTGA(fileName);
		LOG_MSG("Loading image \"" << fileName << "\" from file");
	}
	else
	{
		img = 0;
		LOG_ERROR("Unhandled image file format! File: " << fileName);
	}

	return (img);
}

Image * ImageFactory::CreateImageFromMemory(const MemoryBuffer * memory, const std::string & originalFile)
{
	std::string ext;
	Image * img;

	// Extract file extension:
	ext.assign(originalFile, originalFile.find_last_of('.') + 1, std::string::npos);

	if (stricmp(ext.c_str(), "tga") == 0) // Case-insensitive compare
	{
		img = new ImageTGA(memory, originalFile);
		LOG_MSG("Loading image \"" << originalFile << "\" from memory");
	}
	else
	{
		img = 0;
		LOG_ERROR("Unhandled image file format! File: " << originalFile);
	}

	return (img);
}